//
// C++ Implementation: gpl
//
// Description: Timers dump.
//
//
// Author: Andrey Sharoyko <vanyamboe@gmail.com>, (C) 2011
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "dumpdebug.h"
#include "timers.h"
#include "registers.h"
#include "printpgm.h"

prog_char wgmModett[] PROGMEM = { "WGM mode\t\t" };
prog_char toptt[] PROGMEM = { "TOP\t\t" };
prog_char minus[] PROGMEM = { " - " };
prog_char ocr[] PROGMEM = { "OCR" };
prog_char oc[] PROGMEM = { "OC" };
prog_char tt[] PROGMEM = { "\t\t" };
prog_char ttt[] PROGMEM = { "\t\t\t" };
prog_char clockSourcett[] PROGMEM = { "Clock source\t\t" };
prog_char updateOnOcr[] PROGMEM = { "Update of OCR" };
prog_char xatt[] PROGMEM = { "x at\t" };
prog_char flagSetOnt[] PROGMEM = { " flag set on\t" };
prog_char tcnt[] PROGMEM = { "TCNT" };
prog_char extClockInputt[] PROGMEM = { "External clock input\t" };
prog_char asyncSourcet[] PROGMEM = { "Asynchronous source\t" };
prog_char ioClock[] PROGMEM = { "I/O clock" };
prog_char tosc1[] PROGMEM = { "TOSC1" };
prog_char synchronizationModet[] PROGMEM = { "Synchronization mode\t" };
prog_char inactive[] PROGMEM = { "inactive" };
#define active ((prog_char*)inactive+2)
prog_char outputModet[] PROGMEM = { " Output mode\t" };
prog_char nbit[] PROGMEM = { "-bit" };
prog_char nInvalidTimerNo[] PROGMEM = { "\r\nInvalid timer no.: " };
prog_char nTimertt[] PROGMEM = { "\r\nTimer\t\t" };

prog_char timerMode0[] PROGMEM = { "Normal" };
prog_char timerMode1[] PROGMEM = { "CTC" };
prog_char timerMode2[] PROGMEM = { "Fast PWM" };
prog_char timerMode3[] PROGMEM = { "PWM, Phase correct" };
prog_char timerMode4[] PROGMEM = { "PWM, Phase and Freq. correct" };
prog_char timerMode5[] PROGMEM = { "(Reserved)" };

PROGMEM const char* timerModes[] = {
  timerMode0, timerMode1, timerMode2, timerMode3, timerMode4, timerMode5
};

prog_char timerCS0[] PROGMEM = { "Stopped" };
prog_char timerCS1[] PROGMEM = { " prescaler" };
prog_char timerCS2[] PROGMEM = { "T0 pin." };
prog_char timerCS3[] PROGMEM = { "T1 pin." };
prog_char timerCS4[] PROGMEM = { " Clock on falling edge" };
prog_char timerCS5[] PROGMEM = { " Clock on rising edge" };

PROGMEM const char* timerCS[] = {
  timerCS0, timerCS1, timerCS2, timerCS3, timerCS4, timerCS5
};

prog_char timerWGM0[] PROGMEM = { "-" };
prog_char timerWGM1[] PROGMEM = { "FF" };
prog_char timerWGM2[] PROGMEM = { "OCR0A" };
prog_char timerWGM3[] PROGMEM = { "OCR1A" };
prog_char timerWGM4[] PROGMEM = { "OCR2A" };
prog_char timerWGM5[] PROGMEM = { "Immediate" };
prog_char timerWGM6[] PROGMEM = { "TOP" };
prog_char timerWGM7[] PROGMEM = { "BOTTOM" };
prog_char timerWGM8[] PROGMEM = { "MAX" };
prog_char timerWGM9[] PROGMEM = { "FFFF" };
prog_char timerWGM10[] PROGMEM = { "00FF" };
prog_char timerWGM11[] PROGMEM = { "01FF" };
prog_char timerWGM12[] PROGMEM = { "03FF" };
prog_char timerWGM13[] PROGMEM = { "ICR1" };

PROGMEM const char* timerWGM[] = {
  timerWGM0, timerWGM1, timerWGM2, timerWGM3, timerWGM4, timerWGM5, timerWGM6, timerWGM7, timerWGM8,
  timerWGM9, timerWGM10, timerWGM11, timerWGM12, timerWGM13
};

prog_char timerCOM0[] PROGMEM = { "disconnected" };
prog_char timerCOM1[] PROGMEM = { " on Compare Match" };
prog_char timerCOM2[] PROGMEM = { "Toggle" };
prog_char timerCOM3[] PROGMEM = { "Set" };
prog_char timerCOM4[] PROGMEM = { "Clear" };
prog_char timerCOM5[] PROGMEM = { " when upcounting" };
prog_char timerCOM6[] PROGMEM = { " when downcounting" };
prog_char timerCOM7[] PROGMEM = { "non-inverting)" };
prog_char timerCOM8[] PROGMEM = { "inverting)" };
prog_char timerCOM9[] PROGMEM = { "-" };
prog_char timerCOM10[] PROGMEM = { " at BOTTOM (" };
prog_char timerCOM12[] PROGMEM = { " (Set to low level)" };
prog_char timerCOM13[] PROGMEM = { " (Set to high level)" };

PROGMEM const char* timerCOM[] = {
  timerCOM0, timerCOM1, timerCOM2, timerCOM3, timerCOM4, timerCOM5, timerCOM6, timerCOM7, timerCOM8, timerCOM9, timerCOM10, timerMode5, timerCOM12, timerCOM13
};

prog_char timerIrq0[] PROGMEM = { "TOV" };
prog_char timerIrq1[] PROGMEM = { "COMPA" };
prog_char timerIrq2[] PROGMEM = { "COMPB" };
prog_char timerIrq3[] PROGMEM = { " interrupt\t" };
prog_char timerIrq4[] PROGMEM = { "disabled" };
prog_char timerIrq5[] PROGMEM = { "enabled" };
prog_char timerIrq6[] PROGMEM = { "ICR1" };

PROGMEM const char* timerIrq[] = {
  timerIrq0, timerIrq1, timerIrq2, timerIrq6, timerIrq3, timerIrq4, timerIrq5
};

void dumpOCR(unsigned char tn, bool anb)
{
  printPgm(ocr);
  Serial.print(tn, DEC);
  Serial.println((anb) ? 'A' : 'B');
}

void dumpOC(unsigned char tn, bool anb)
{
  printPgm(oc);
  Serial.print(tn, DEC);
  Serial.print((anb) ? 'A' : 'B');
}

void dumpCOM(timerdesc_t* td, unsigned char bna)
{
  dumpOC(td->timer, !bna);
  printPgm(outputModet);
  Serial.print(td->com[bna], DEC);
  printPgm(timerWGM0);
  printPgm(&(timerCOM[td->com0[bna]]));
  if(td->com1[bna])
    printPgm(&(timerCOM[td->com1[bna]]));
  if(td->com2[bna])
    printPgm(&(timerCOM[td->com2[bna]]));
  Serial.println();
  if(td->com3[bna]) {
    printPgm(tt);
    printPgm(&(timerCOM[td->com3[bna]]));
  }
  else
    return;
  if(td->com4[bna])
    printPgm(&(timerCOM[td->com4[bna]]));
  if(td->com5[bna])
    printPgm(&(timerCOM[td->com5[bna]]));
  Serial.println();
}

void dumpInterrupt(timerdesc_t* td, unsigned char irq)
{
  printPgm(&(timerIrq[irq]));
  printPgm(&(timerIrq[4]));
  printlnPgm(&(timerIrq[5+td->irqen[irq]]));
}

void dumpTimerDesc(timerdesc_t* td)
{
  printPgm(clockSourcett);
  if(td->presc) {
    Serial.print('/');
    Serial.print(td->presc, DEC);
  }
  printPgm(&(timerCS[td->clksrc]));
  if(td->clkedge)
    printPgm(&(timerCS[td->clkedge]));
  Serial.println();
  printPgm(wgmModett);
  Serial.print(td->wgm, DEC);
  printPgm(minus);
  printPgm(&(timerModes[td->mode]));
  if(td->bits) {
    Serial.print(' ');
    Serial.print(td->bits, DEC);
    printPgm(nbit);
  }
  Serial.println();
  printPgm(toptt);
  printlnPgm(&(timerWGM[td->top]));
  printPgm(updateOnOcr);
  Serial.print(td->timer, DEC);
  printPgm(xatt);
  printlnPgm(&(timerWGM[td->updocr]));
  printPgm(timerIrq0);
  Serial.print(td->timer, DEC);
  printPgm(flagSetOnt);
  printlnPgm(&(timerWGM[td->tovint]));
  dumpCOM(td, 0);
  dumpCOM(td, 1);
  dumpInterrupt(td, 0);
  dumpInterrupt(td, 1);
  dumpInterrupt(td, 2);
  if(td->timer == 1)
    dumpInterrupt(td, 3);
}

void initTimerDesc(timerdesc_t* td)
{
  unsigned char* p = (unsigned char*) td;
  for(unsigned char n=0; n<sizeof(timerdesc_t); ++n, ++p)
    *p = 0;
}

void descCS(timerdesc_t *td)
{
  td->presc = 0;
  switch(td->clksrc) {
    case 0:
      td->clksrc = 0;
      break;
    case 1:
      td->presc = 1;
      break;
    case 2:
      td->presc = 8;
      break;
    case 3:
      td->presc = (td->timer == 2) ? 32 : 64;
      break;
    case 4:
      td->presc = (td->timer == 2) ? 64 : 256;
      break;
    case 5:
      td->presc = (td->timer == 2) ? 128 : 1024;
      break;
    case 6:
      if(td->timer == 2)
        td->presc = 256;
      else {
        if(td->timer == 0)
            td->clksrc  = 2;
        else
            td->clksrc = 3;
        td->clkedge = 4;
      }
      break;
    case 7:
      if(td->timer == 2)
        td->presc = 1024;
      else {
        if(td->timer == 0)
            td->clksrc  = 2;
        else
            td->clksrc  = 3;
        td->clkedge = 5;
      }
      break;
  }
  if(td->presc)
    td->clksrc = 1;
}

void descWGM(timerdesc_t *td)
{
  switch(td->wgm) {
    case 0:
      td->mode = 0;
      break;
    case 1:
    case 5:
      td->mode = 3;
      break;
    case 2:
      td->mode = 1;
      break;
    case 3:
    case 7:
      td->mode = 2;
      break;
    default:
      td->mode = 5;
      break;
  }
  switch(td->wgm) {
    case 0:
    case 1:
    case 3:
      td->top = 1;
      break;
    case 2:
    case 5:
    case 7:
      td->top = td->timer ? 4 : 2;
      break;
    default:
      td->top = 0;
  }
  switch(td->wgm) {
    case 0:
    case 2:
      td->updocr = 5;
      break;
    case 1:
    case 5:
      td->updocr = 6;
      break;
    case 3:
    case 7:
      td->updocr = 7;
      break;
    default:
      td->updocr = 0;
  }
  switch(td->wgm) {
    case 0:
    case 2:
    case 3:
      td->tovint = 8;
      break;
    case 1:
      td->tovint = 7;
      break;
    case 7:
      td->tovint = 6;
      break;
    default:
      td->tovint = 0;
  }
}

void descWGM16(timerdesc_t* td)
{
  unsigned char wgm = td->wgm;
  switch(wgm) {
    case 0:
      td->mode = 0;
      break;
      
    case 1:
    case 2:
    case 3:
    case 10:
    case 11:
      td->mode = 3;
      if(wgm < 10)
        td->bits = 7 + wgm;
      break;
      
     case 4:
     case 12:
      td->mode = 1;
      break;
     
     case 5:
     case 6:
     case 7:
     case 14:
     case 15:
      td->mode = 2;
      if(wgm < 14)
        td->bits = 3 + wgm;
      break;
      
     case 8:
     case 9:
      td->mode = 4;
      break;
      
     case 13:
      td->mode = 5;
      break;
  }
  switch(wgm) {
    case 0:
      td->top = 9;
      break;
    case 1:
    case 5:
      td->top = 10;
      break;
    case 2:
    case 6:
      td->top = 11;
      break;
    case 3:
    case 7:
      td->top = 12;
      break;
    case 4:
    case 9:
    case 11:
    case 15:
      td->top = 3;
      break;
    case 8:
    case 10:
    case 12:
    case 14:
      td->top = 13;
      break;
    default:
      td->top = 0;
  }
  switch(wgm) {
    case 0:
    case 4:
    case 12:
      td->updocr = 5;
      break;
    case 1:
    case 2:
    case 3:
    case 10:
    case 11:
      td->updocr = 6;
      break;
    case 13:
      td->updocr = 0;
      break;
    default:
      td->updocr = 7;
      break;
  }
  switch(wgm) {
    case 0:
    case 4:
    case 12:
      td->tovint = 8;
      break;
    case 1:
    case 2:
    case 3:
    case 10:
    case 11:
      td->tovint = 7;
      break;
    case 13:
      td->tovint = 0;
      break;
    default:
      td->tovint = 6;
      break;
  }
}

void descCOM(timerdesc_t* td, unsigned char bna)
{
  unsigned char wgm = td->wgm;
  unsigned char com = td->com[bna];
  if (wgm == 4 || wgm == 6) {
    td->com0[bna] = 9;
    return;
  }
  switch(com) {
    case 0:
      td->com0[bna] = 0;
      break;

    case 1:
      switch(bna) {
        case 1:
          if(wgm == 0 || wgm == 2) {
            td->com0[bna] = 2;
            td->com1[bna] = 1;
          }
          else
            td->com0[bna] = 11;
          break;
        case 0:
          if(wgm == 3)
            td->com0[bna] = 0;
          else {
            td->com0[bna] = 2;
            td->com1[bna] = 1;
          }
          break;
      }
      break;

    case 2:
    case 3:
      if(td->com[bna] == 2)
        td->com0[bna] = 4;
      else
        td->com0[bna] = 3;
      td->com1[bna] = 1;
      if(wgm == 1 || wgm == 5)
        td->com2[bna] = 5;
      if(wgm == 3 || wgm == 7 || wgm == 1 || wgm == 5) {
        if(com == 2)
          td->com3[bna] = 3;
        else
          td->com3[bna] = 4;
        if(wgm == 3 || wgm == 7) {
          td->com4[bna] = 10;
          if(com == 2)
            td->com5[bna] = 7;
          else
            td->com5[bna] = 8;
        }
        else {
          td->com4[bna] = 1;
          td->com5[bna] = 6;
        }
      }
      break;
  }
}

void descCOM16(timerdesc_t* td, unsigned char bna)
{
  unsigned char wgm = td->wgm;
  unsigned char com = td->com[bna];
  if (wgm == 13) {
    td->com0[bna] = 9;
    return;
  }
  switch(com) {
    case 0:
      td->com0[bna] = 0;
      break;
      
    case 1:
       if((wgm == 0 || wgm == 4 || wgm == 12) || (bna == 0 && (wgm > 14 || wgm == 9 || wgm == 11))) {
         td->com0[bna] = 2;
         td->com1[bna] = 1;
       }
       else
         td->com0[bna] = 0;
       break;
       
     case 2:
       td->com0[bna] = 4;
       td->com1[bna] = 1;
       if (wgm != 0 && wgm != 4 && wgm != 12 && wgm != 8 && wgm != 9) {
         td->com3[bna] = 3;
         td->com4[bna] = 10;
         td->com5[bna] = 7;
       }
       else if (wgm == 8 || wgm == 9) {
         td->com2[bna] = 5;
         td->com3[bna] = 3;
         td->com4[bna] = 1;
         td->com5[bna] = 6;
       }
       else
         td->com2[bna] = 12;
       break;
     case 3:
       td->com0[bna] = 3;
       td->com1[bna] = 1;
       if (wgm != 0 && wgm != 4 && wgm != 12 && wgm != 8 && wgm != 9) {
         td->com3[bna] = 4;
         td->com4[bna] = 10;
         td->com5[bna] = 8;
       }
       else if (wgm == 8 || wgm == 9) {
         td->com2[bna] = 5;
         td->com3[bna] = 4;
         td->com4[bna] = 1;
         td->com5[bna] = 6;
       }
       else
         td->com2[bna] = 13;
       break;
  }  
}

void descTIMSK(timerdesc_t* td)
{
  td->irqen[0] = td->timsk & 1;
  td->irqen[1] = (td->timsk & 2) >> 1;
  td->irqen[2] = (td->timsk & 4) >> 2;
  td->irqen[3] = (td->timsk & 8) >> 3;
}

void dumpTCNT(unsigned char valueh, unsigned char tn, unsigned char valuel = 0)
{
  printPgm(tcnt);
  Serial.print(tn, DEC);
  printPgm(tt);
  dumpHexByte(valueh);
  if (DUMP_IS_16BIT_TIMER(tn))
    dumpHexByte(valuel);
  Serial.println();
}

void dumpOCRx(unsigned char valueh, unsigned char tn, unsigned char ocrt, unsigned char valuel = 0)
{
  printPgm(ocr);
  Serial.print(tn, DEC);
  Serial.print((char)('A' + ocrt));
  printPgm(tt);
  dumpHexByte(valueh);
  if (DUMP_IS_16BIT_TIMER(tn))
    dumpHexByte(valuel);
  Serial.println();
}

void dumpASSR(bool excl, bool as2)
{
  printPgm(extClockInputt);
  printlnPgm(&(timerIrq[5+excl]));
  printPgm(asyncSourcet);
  if(as2)
    printlnPgm(ioClock);
  else
    printlnPgm(tosc1);
}

void dumpTSM(bool tsm)
{
  printPgm(synchronizationModet);
  if(tsm)
    printlnPgm(active);
  else
    printlnPgm(inactive);
}

void dumpTimer8bit(unsigned char ntm, unsigned char addr)
{
  timerdesc_t      td;
  initTimerDesc(&td);
  td.timer = ntm;
  td.clksrc = _SFR_MEM8(addr+1) & 7;
  td.wgm = (_SFR_MEM8(addr) & 3) | ((_SFR_MEM8(addr+1) & 8) >> 1);
  td.com[0] = (_SFR_MEM8(addr) & 0x30) >> 4;
  td.com[1] = (_SFR_MEM8(addr) & 0xC0) >> 6;
  td.timsk   = _SFR_MEM8((0x6E + ntm)) & 7;
  descCS(&td);
  descWGM(&td);
  descCOM(&td, 0);
  descCOM(&td, 1);
  descTIMSK(&td);
  dumpTimerDesc(&td);
  dumpTCNT(_SFR_MEM8(addr+2), ntm);
  dumpOCRx(_SFR_MEM8(addr+3), ntm, 0);
  dumpOCRx(_SFR_MEM8(addr+4), ntm, 1);
  if (ntm == 2)
    dumpASSR(ASSR & 0x40, ASSR & 0x20);
  dumpTSM(GTCCR & 0x80);
}

void dumpTimer16bit(unsigned char ntm, unsigned int addr)
{
  timerdesc_t      td;
  initTimerDesc(&td);
  td.timer = ntm;
  td.clksrc = (_SFR_MEM8(addr+1) & 7);
  td.wgm = (_SFR_MEM8(addr) & 3) | ((_SFR_MEM8(addr+1) & 0x18) >> 1);
#if defined(DUMP_MEGA)
  td.com[2] = ((_SFR_MEM8(addr) & 0x0C) >> 2);
#endif
  td.com[1] = ((_SFR_MEM8(addr) & 0x30) >> 4);
  td.com[0] = ((_SFR_MEM8(addr) & 0xC0) >> 6);
  td.timsk   = (_SFR_MEM8((0x6E + ntm)) & 7) | ((_SFR_MEM8((0x6E + ntm)) & 0x20) >> 2);
  descCS(&td);
  descWGM16(&td);
  descCOM16(&td, 0);
  descCOM16(&td, 1);
#if defined(DUMP_MEGA)
  descCOM16(&td, 2);
#endif
  descTIMSK(&td);
  dumpTimerDesc(&td);
  dumpTCNT(_SFR_MEM8(addr+5), ntm, _SFR_MEM8(addr+4));
  dumpOCRx(_SFR_MEM8(addr+9), ntm, 0, _SFR_MEM8(addr+8));
  dumpOCRx(_SFR_MEM8(addr+11), ntm, 1, _SFR_MEM8(addr+10));
#if defined(DUMP_MEGA)
  dumpOCRx(_SFR_MEM8(addr+13), ntm, 2, _SFR_MEM8(addr+12));
#endif
  dumpTSM(GTCCR & 0x80);
}

void dumpTimer(unsigned char tn)
{
  if(tn >= DUMP_TIMERS_COUNT) {
    printPgm(nInvalidTimerNo);
    Serial.println(tn, DEC);
    return;
  }
  printPgm(nTimertt);
  Serial.println(tn, DEC);
  if (DUMP_IS_16BIT_TIMER(tn)) {
#if defined(DUMP_ARDUINO)
    dumpTimer16bit(tn, 0x80);
#elif defined(DUMP_MEGA)
     unsigned int treg;
     if (tn == 1) treg = 0x80;
     else if (tn == 3) treg = 0x90;
     else if (tn == 4) treg = 0xA0;
     else treg = 0x120;
     dumpTimer16bit(tn, treg);
#else
#   error "TODO: Add board type support here."
#endif
  }
  else
    dumpTimer8bit(tn, (tn == 0) ? 0x44 : 0xb0);
}

